package com.github.ylt.protocol.http.server;

import com.github.ylt.conf.Configure;
import com.github.ylt.protocol.event.Event;
import com.github.ylt.protocol.http.*;
import com.github.ylt.protocol.http.handler.HttpHandler;
import com.github.ylt.protocol.http.handler.HttpHandlerImpl;
import com.github.ylt.protocol.http.server.exchange.HttpExchangeImpl;
import com.google.common.util.concurrent.MoreExecutors;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.*;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Server的实现，目前基本为HTTPServer的实现
 * 目前不支持HTTPS，只实现http
 */
public class ServerImpl extends Server {

    private final String protocol;
    private final Selector selector;
    private final ServerSocketChannel channel;
    private final SelectionKey selectionKey;
    private InetSocketAddress localAddress;
    private Executor executor;
    private final Set<HttpConnection> allConnections;
    private final Set<HttpConnection> requestedConnections;
    private final Set<HttpConnection> acceptedConnections;
    private final Set<HttpConnection> responseConnections;
    private final Set<HttpConnection> idleConnections;
    private final Timer timer;
    private Thread dispatcherThread;
    private final long MAX_IDLE_CONNECTION = Configure.DEFAULT_MAX_IDLE_CONNECTION;
    private boolean finish = false;
    private volatile boolean terminate;
    private final Dispatcher dispatcher;
    private boolean bind = false;
    private int exchangeCount = 0;
    private final HttpHandler httpHandler = new HttpHandlerImpl();
    private final List<Event> events = new LinkedList<>();
    private final ReentrantLock lock = new ReentrantLock();
    public ServerImpl(String protocol, InetSocketAddress address) {
        try {
            this.channel = ServerSocketChannel.open();
            if(address != null){
                this.channel.socket().bind(address,0);
                this.bind = true;
            }
            selector = Selector.open();
            channel.configureBlocking(false);
            this.selectionKey = channel.register(selector, SelectionKey.OP_ACCEPT);
            this.allConnections = Collections.synchronizedSet(new HashSet<>());
            this.requestedConnections = Collections.synchronizedSet(new HashSet<>());
            this.acceptedConnections = Collections.synchronizedSet(new HashSet<>());
            this.responseConnections = Collections.synchronizedSet(new HashSet<>());
            this.idleConnections = Collections.synchronizedSet(new HashSet<>());
            this.timer = new Timer("idle-connection-timer", true);
            this.timer.schedule(new IdleConnectionTask(),Configure.DEFAULT_IDLE_CONNECTION_TIMER_DELAY,Configure.DEFAULT_IDLE_CONNECTION_TIMER_PERIOD);
            this.dispatcher = new Dispatcher();

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        this.protocol = protocol;
        this.localAddress = address;

    }


    @Override
    public void start() {
        if(executor == null){
            executor = MoreExecutors.directExecutor();
        }
        this.dispatcherThread = new Thread(null,dispatcher,"dispatcher-thread",0);
        this.dispatcherThread.start();
    }
    @Override
    public void stop() {
        this.terminate = true;
        try{
            channel.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        finish = true;
        selector.wakeup();
        synchronized (allConnections) {
            for (HttpConnection connection : allConnections) {
                connection.close();
            }
        }
        allConnections.clear();
        requestedConnections.clear();
        acceptedConnections.clear();
        responseConnections.clear();
        idleConnections.clear();
        timer.cancel();
        if(dispatcher != null && dispatcherThread != Thread.currentThread()){
            try {
                dispatcherThread.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    @Override
    public InetSocketAddress getAddress() {
        return localAddress;
    }

    @Override
    public int getPort() {
        return localAddress.getPort();
    }

    @Override
    public void bind(InetSocketAddress address) {
        if(bind){
            throw new IllegalStateException("服务器已经绑定");
        }
        if(address == null){
            throw new IllegalArgumentException("地址不能为空！");
        }
        this.localAddress = address;
        try {
            this.channel.socket().bind(address);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void close() {

    }

    @Override
    public boolean isClosed() {
        return false;
    }

    @Override
    public HttpContext getContext() {
        return null;
    }

    @Override
    public void createContext(String path) {

    }

    @Override
    public void removeContext(String path) {

    }

    @Override
    public void removeContext(HttpContext context) {

    }
    public synchronized void startExchange () {
        exchangeCount ++;
    }
    public synchronized int endExchange () {
        exchangeCount --;
        assert exchangeCount >= 0;
        return exchangeCount;
    }

    public void registerIdleConnection(HttpConnection connection) {
        boolean closed = false;
        synchronized (idleConnections) {
            if(idleConnections.size() >= MAX_IDLE_CONNECTION){
                closed = true;
            }else{
                connection.setIdleStartTime(System.currentTimeMillis());
                connection.setState(HttpConnection.State.IDLE);
                idleConnections.add(connection);
            }
        }
        if(closed){
            connection.close();
            System.out.println("已经达到最大的请求,新的链接已经被丢弃，链接："+ connection);
        }
    }
    public void registerRequestConnection(HttpConnection connection) {
        connection.setRequestStartTime(System.currentTimeMillis());
        connection.setState(HttpConnection.State.REQUEST);
        requestedConnections.add(connection);
        System.out.println("已经有请求连接进入");
    }

    public void registerResponseConnection(HttpConnection connection) {
        requestedConnections.remove(connection);
        connection.setResponseStartTime(System.currentTimeMillis());
        connection.setState(HttpConnection.State.RESPONSE);
        responseConnections.add(connection);
        System.out.println("已经注册响应连接");
    }

    public void registerAcceptedConnection(HttpConnection connection) {
        connection.setIdleStartTime(System.currentTimeMillis());
        connection.setState(HttpConnection.State.ACCEPTED);
        acceptedConnections.add(connection);
        System.out.println("已经和客户端连接，连接请求注册完毕");
    }

    public void closeConnection(HttpConnection connection) {
        connection.close();
        allConnections.remove(connection);
        switch (connection.getState()){
            case IDLE:
                idleConnections.remove(connection);
                break;
            case REQUEST:
                requestedConnections.remove(connection);
                break;
            case RESPONSE:
                responseConnections.remove(connection);
                break;
            case ACCEPTED:
                acceptedConnections.remove(connection);
                break;
        }
    }

    class Dispatcher implements Runnable{

        @Override
        public void run() {
            while (!finish){
                try {
                    lock.lock();
                    try{
                        for (Event event : events) {
                            if(event.eventType() == Event.EventType.RESPONSE_COMPLETE){
                                responseConnections.remove(event.connection());
                                registerIdleConnection(event.connection());
                            }else if(event.eventType() == Event.EventType.REQUEST_COMPLETION){
                                requestedConnections.remove(event.connection());
                                responseConnections.add(event.connection());
                            }
                        }
                        events.clear();
                    }finally {
                        lock.unlock();
                    }
                    selector.select(1000);
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while (iterator.hasNext()){
                        SelectionKey key = iterator.next();
                        iterator.remove();
                        if(key.equals(selectionKey)){
                            SocketChannel accept = channel.accept();
                            if(accept != null){
                                if(terminate){
                                    continue;
                                }
                                if(allConnections.size() >= MAX_IDLE_CONNECTION){
                                    System.out.println("连接被关闭");
                                    accept.close();
                                    continue;
                                }
                                accept.socket().setTcpNoDelay(true);
                                accept.configureBlocking(false);
                                SelectionKey register = accept.register(selector, SelectionKey.OP_READ);
                                HttpConnection connection = new HttpConnection();
                                connection.setSelectionKey(register);
                                connection.setSocketChannel(accept);
                                register.attach(connection);
                                registerAcceptedConnection(connection);
                                allConnections.add(connection);
                            }
                        }else{
                           try{
                               if(key.isReadable()){
                                   SocketChannel channel1 = (SocketChannel) key.channel();
                                   HttpConnection attachment = (HttpConnection) key.attachment();
                                   key.cancel();
                                   channel1.configureBlocking(true);
                                   if(acceptedConnections.remove(attachment) || idleConnections.remove(attachment)){
                                       registerRequestConnection(attachment);
                                   }
                                   handle(channel1,attachment);
                               }
                           }catch (CancelledKeyException | IOException e){
                               System.out.println("连接正在关闭3");
                               HttpConnection attachment = (HttpConnection) key.attachment();
                               closeConnection(attachment);
                              e.printStackTrace();
                           }
                        }

                    }
                    selector.selectNow();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            try {
                System.out.println("selector被关闭");
                selector.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        public void handle (SocketChannel chan, HttpConnection conn){
            try {
                Exchange t = new Exchange(protocol,conn,chan);
                executor.execute (t);
            } catch (Throwable e) {
                closeConnection(conn);
            }
        }
    }

    public void handlerEvent(Event event){
        lock.lock();
        try{
            this.events.add(event);
            selector.wakeup();
        }finally {
            lock.unlock();
        }
    }

    class IdleConnectionTask extends TimerTask {

        @Override
        public void run() {
            ArrayList<HttpConnection> toRemove = new ArrayList<>();
            final long time = System.currentTimeMillis();
            synchronized (idleConnections) {
                for (HttpConnection connection : idleConnections) {
                    if (time - connection.getIdleStartTime() >= 800) {
                        toRemove.add(connection);
                    }
                }
            }
            for (HttpConnection connection : toRemove) {
                idleConnections.remove(connection);
                connection.close();
            }
        }
    }

    class Exchange implements Runnable{
        SocketChannel socketChannel;
        HttpConnection connection;
        InputStream rawInput;
        HttpContext context;
        OutputStream rawOutput;
        String protocol;

        Exchange(String protocol, HttpConnection connection,SocketChannel socketChannel){
            this.protocol = protocol;
            this.connection = connection;
            this.socketChannel = socketChannel;
        }

        @Override
        public void run() {
            if(dispatcherThread == Thread.currentThread()){
                try {
                    selector.selectNow();
                } catch (IOException e) {
                    closeConnection(connection);
                    throw new RuntimeException(e);
                }
            }
            idleConnections.remove(connection);
            context = connection.getContext();
            rawInput = new BufferedInputStream(new Request.ReadStream(socketChannel,ServerImpl.this));
            rawOutput = new BufferedOutputStream(new Request.WriteStream(socketChannel,ServerImpl.this));
            connection.setRawInputStream(rawInput);
            connection.setRawOutputStream(rawOutput);
            Request request = new Request(rawInput,rawOutput);
            String protocolLine = request.getProtocolLine();
            if(protocolLine == null){
                closeConnection(connection);
                return;
            }
            int i = protocolLine.indexOf(0x20);
            if(i == -1){
                System.out.println("1返回："+ Code.BAD_REQUEST);
                return;
            }
            String[] split = protocolLine.split(" ");
            URI url;
            try {
                url = new URI(split[1]);
            } catch (URISyntaxException e) {
                System.out.println("2返回："+ Code.BAD_REQUEST);
                return;
            }
            Headers headers;
            try {
                headers = request.getHeaders();
                System.out.println(((ImmutableHeaders)headers).getHeaderString());
                String first ="0";
                if(headers.containsKey("Content-Length")){
                    first = headers.get("Content-Length").getFirst();
                }
                if(Integer.parseInt(first) == 0){
                    registerResponseConnection(connection);
                }
                //目前不进行其他http返回了，只对404进行返回，404由httphandler处理
                HttpContextImpl http = new HttpContextImpl(split[1], "http", ServerImpl.this, httpHandler);
                connection.setContext(http);
                com.github.ylt.protocol.http.server.exchange.Exchange exchange = new com.github.ylt.protocol.http.server.exchange.Exchange(split[0],url,Integer.parseInt(first),request,connection);
                HttpExchangeImpl httpExchange = new HttpExchangeImpl(exchange);
                httpHandler.handler(httpExchange);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
